#include "WebSocketClient.h"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS)  
#include <unistd.h>
#endif
static WebSocketClient *s_SharedInstance = nullptr;

WebSocketClient* WebSocketClient::getInstance()
{
	if (!s_SharedInstance)
	{
		s_SharedInstance = new (std::nothrow) WebSocketClient();
		s_SharedInstance->m_nConnectState = 0;
		CCASSERT(s_SharedInstance, "FATAL: Not enough memory");
		s_SharedInstance->init();
	}

	return s_SharedInstance;
}

WebSocketClient::WebSocketClient()
{
}

WebSocketClient::~WebSocketClient()
{
}

void WebSocketClient::init()
{
	CCLOG(GlobalData::getInstance()->getWebSocketUrl().c_str());
	conect();
	m_messageHandler = new MessageHandler;
	std::thread t1(&WebSocketClient::sendTh, this); 
	t1.detach();
}

void WebSocketClient::conect()
{
	if (m_nConnectState == 0)
	{
		m_nConnectState = 1;//conecting
		std::string param = CCString::createWithFormat("&terminal_id=%s&version=%s",
			GlobalData::getInstance()->getIdentity().c_str(),
			VERSION)->getCString();
		m_pSocketIOClient = SocketIO::connect(GlobalData::getInstance()->getWebSocketUrl(), param.c_str(), *this);
		m_pSocketIOClient->setTag(GlobalData::getInstance()->getTerminalId().c_str());
		m_pSocketIOClient->on("connect", CC_CALLBACK_1(WebSocketClient::onConnect, this));
		//LOG_TO_FILE("client conect");
	}
}

bool WebSocketClient::isOpen() 
{
	if (m_pSocketIOClient != NULL)
	{
		return true;
	}

	return false;
}

void WebSocketClient::onConnect(cocos2d::network::SIOClient* client)
{
	m_nConnectState = 2;//open
	CCLOG("onConnect");
	//LOG_TO_FILE("client on conect");
}

void WebSocketClient::onMessage(cocos2d::network::SIOClient* client, const std::string& data)
{
	std::string strData = data.substr(1, data.length() - 2);
	unsigned char *cB64Message;
	int length = base64Decode((unsigned char*)strData.c_str(), (unsigned int)strData.length(), &cB64Message);
	CCString* content = CCString::createWithFormat("%s", cB64Message);
	std::string sData = std::string(content->getCString());
	sData = sData.substr(0, length);
	handle(sData.c_str());
	LOG_TO_FILE(CCString::createWithFormat("client on raw:%s", data.c_str())->getCString());
	LOG_TO_FILE(CCString::createWithFormat("client on message:%s", sData.c_str())->getCString());
}

void WebSocketClient::onClose(cocos2d::network::SIOClient* client)
{
	m_nConnectState = 0;//close
	m_pSocketIOClient = NULL;
	CCLOG("onClose");
	LOG_TO_FILE("client on close");
}

void WebSocketClient::onError(cocos2d::network::SIOClient* client, const std::string& error)
{
	/*if (client == m_pSocketIOClient)
	{
		char buf[100] = { 0 };
		sprintf(buf, "an error was fired, code: %s", error.c_str());
	}*/

	m_nConnectState = 0;//close
	m_pSocketIOClient = NULL;
	//CCLOG("Error was fired, error code: %s", error.c_str());

	//LOG_TO_FILE(CCString::createWithFormat("client on error:%s", error.c_str())->getCString());
}

void WebSocketClient::sendTest()
{
	m_messageQueueMutex.lock();
	CCString* mess = CCString::create("{\"content\":\"test\"}");
	mess->retain();
	m_messageQueue.push_back(mess);
	m_messageQueueMutex.unlock();
}

std::string WebSocketClient::makeApiJson(const char* apiName, ValueMap mess)
{
	rapidjson::Document document;
	rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
	rapidjson::Value root(kObjectType);
	//rapidjson::Value api(kStringType);
	//std::string apiStr(apiName);
	//api.SetString(apiStr.c_str(), apiStr.size(), allocator);
	//root.AddMember("api", api, allocator);

	rapidjson::Value param(kObjectType);
	for (auto info : mess)
	{
		rapidjson::Value filedName(kStringType);
		filedName.SetString(info.first.c_str(), info.first.size(), allocator);
		rapidjson::Value filed(kStringType);
		filed.SetString(info.second.asString().c_str(), info.second.asString().size(), allocator);
		root.AddMember(filedName, filed, allocator);
	}

	//root.AddMember("param", param, allocator);
	StringBuffer buffer;
	Writer<StringBuffer> writer(buffer);
	root.Accept(writer);

	return buffer.GetString();
}

void WebSocketClient::send(const char* mess)
{
	m_messageQueueMutex.lock();
	CCString* messStr = CCString::create(mess);
	messStr->retain();
	m_messageQueue.push_back(messStr);
	m_messageQueueMutex.unlock();
}
void WebSocketClient::send(const char* apiName, ValueMap mess)
{
	CCString* messStr = CCString::create(makeApiJson(apiName, mess).c_str());
	messStr->retain();
	m_messageQueue.push_back(messStr);

	CCString* apiStr = CCString::create(apiName);
	apiStr->retain();
	m_messageApiQueue.push_back(apiStr);

	//LOG_TO_FILE("send api:%s mess:%s", apiStr->getCString(), messStr->getCString());
}
void WebSocketClient::sendImmediately(const char* mess)
{
	if (m_pSocketIOClient != NULL)
	{
		m_pSocketIOClient->send(mess);
	}
	else
	{
		conect();
	}
}
void WebSocketClient::sendImmediately(const char* apiName,ValueMap mess)
{
	if (m_pSocketIOClient != NULL && m_nConnectState == 2)
	{
		std::string load_str;
		load_str = std::string((const char*)makeApiJson(apiName,mess).c_str());
		char *cB64Message;
		int length = base64Encode((unsigned char*)load_str.c_str(), (unsigned int)load_str.length(), &cB64Message);
		std::string sData = std::string(cB64Message);
		sData = sData.substr(0, length);
		m_pSocketIOClient->emit(apiName, sData);
	}
	else
	{
		conect();
	}
}


void WebSocketClient::sendTh()
{
	int countTime = 0;
	while (true) 
	{
		m_messageQueueMutex.lock();
		if (WebSocketClient::getInstance()->m_pSocketIOClient != NULL
			&& (m_nConnectState == 1 || m_nConnectState == 2))
		{
			if (m_nConnectState == 2)
			{
				//if (countTime > 30 * 1000)
				//{
				//	//heatbeat
				//	WebSocketClient::getInstance()->m_pSocketIOClient->emit("hh", "");
				//	countTime = 0;
				//}

				if (m_messageQueue.size() > 0)
				{
					String *msg = *(m_messageQueue.begin());
					String *api = *(m_messageApiQueue.begin());
					//make base64
					std::string load_str;
					load_str = std::string((const char*)msg->getCString());
					char *cB64Message;
					int length = base64Encode((unsigned char*)load_str.c_str(), (unsigned int)load_str.length(), &cB64Message);
					std::string sData = std::string(cB64Message);
					sData = sData.substr(0, length);
					WebSocketClient::getInstance()->m_pSocketIOClient->emit(api->getCString(), sData.c_str());
					m_messageQueue.pop_front();
					m_messageApiQueue.pop_front();
					LOG_TO_FILE(CCString::createWithFormat("send thread api:%s raw:%s", api->getCString(), load_str.c_str())->getCString());
					LOG_TO_FILE(CCString::createWithFormat("send thread api:%s mess:%s", api->getCString(), sData.c_str())->getCString());
					CC_SAFE_RELEASE(msg);
					CC_SAFE_RELEASE(api);
				}
			}
		}
		else
		{
			//reconect
			if (countTime > 30 * 1000) 
			{
				countTime = 0;
				conect();
			}
		}
		countTime = (countTime + 100);
		m_messageQueueMutex.unlock();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS)  
		usleep(100000);
#elif(CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 )
		Sleep(100);
#endif
	}
}

void WebSocketClient::handle(const char* mess)
{
	if (!(mess[0] == '{' || mess[0] == '['))
	{
		CCLOG("json check failed");
		return;
	}
	Document* pReaddoc = new Document;
	try
	{
		pReaddoc->Parse<0>(mess);
		if (pReaddoc->HasParseError())
		{
			CCLOG("parse failed");
			delete pReaddoc;
			pReaddoc = NULL;
			return;
		}
		m_messageHandler->handle(pReaddoc);
	}
	catch (...)
	{
		CCLOG("parse error");
	}
	delete pReaddoc;
	pReaddoc = NULL;
}

int WebSocketClient::getConnectState()
{
	return m_nConnectState;
}